#include "common.h"
#include "msn_sb.h"
#include "msn_command.h"
#include "sock.h"
#include "msn_msg.h"

static void msn_sb_connect_finish(int fd, int error, int res, void *data);
static void msn_sb_cmd_send_done(int fd, int error, int res, void *data);
/* ans */
static void msn_ans_send(msn_sb_session_t *s);
static void msn_ans_send_finish(int fd, int error, int res, void *data);
/* read and write event */
static void msn_read_from_sb(int fd, short flag, void *arg);
/* MSG */
static void msn_sb_msg_response(msn_sb_session_t *s, const char *buf, const int size);
static void msn_sb_msg_response_done(int fd, int error, int res, void *data);
static GString *msn_sb_msg_typing(msn_sb_session_t *s);
static void msn_sb_msg_typing_send_done(int fd, int error, int res, void *data);


void msn_rng_handle(const char *line, const int size, msn_session_t *robot)
{
	assert(line && robot);
	msn_sb_session_t *sess;

	sess = msn_sb_session_new(robot, NULL);
	if(!sess) {
		debug_log(1, "%s", "failed to init sb session\n");
		return ;
	}
	msn_sb_session_init(line, size, sess);
}

msn_sb_session_t *msn_sb_session_init(const char *line, const int size, msn_sb_session_t *sess)
{
	g_return_val_if_fail(line && sess, NULL);
	
	int i = 0;
	char *token, *last;
	char ip[16];
	int port;
	char buf[MAX_BUFFER];
	const char *sep = W_SPACE;
	struct sockaddr_in srv;
	
	assert(MAX_BUFFER >= size);
	sess->in = g_string_new(NULL);
	memcpy(buf, line, size);
	buf[size] = '\0';
	for(token = strtok_r(buf, sep, &last); token; token = strtok_r(NULL, sep, &last), i++) {
		if(!strcasecmp(token, "RNG") &&  i == 0) 
			sess->type = MSN_SB_TYPE_RNG;
		else if(!strcasecmp(token, "XFR") && i == 0)
			sess->type = MSN_SB_TYPE_XFR;
		
		if(sess->type == MSN_SB_TYPE_RNG) {
			switch(i) {
			case 1: 
				sess->session_id = g_string_new(token);
				break;
			case 2: {
				sock_ip_port_parse(token, ip, &port);
				sess->port = port;
				sess->fd = sock_open_from_host(ip, &sess->addr);
				sess->ev = xevent_new();
				xevent_set(sess->ev, sess->fd, SOCK_FLAG_WRITE, NULL, NULL);
				xevent_base_set(sess->ev, master_base);
				break;
			}
			case 4:
				sess->ticket = g_string_new(token);
				break;
			case 5:{
				GString *t = g_string_new(token);
				debug_log(9, "find %s\n", t->str);
				sess->creator = g_hash_table_lookup(sess->ms->friends, t);
				g_string_free(t, 1);
				break;
				   }
			default:
				break;
			}
		}
		else if(sess->type == MSN_SB_TYPE_XFR) {
			switch(i) {
				case 3: {
				sock_ip_port_parse(token, ip, &port);
				sess->port = port;
				sess->fd = sock_open_from_host(ip, &sess->addr);
				sess->ev = xevent_new();
				xevent_set(sess->ev, sess->fd, SOCK_FLAG_WRITE, NULL, NULL);
				xevent_base_set(sess->ev, master_base);
				break;
				}
				case 5:
					sess->ticket = g_string_new(token);
					break;
			}
		}
	}
	srv.sin_family = AF_INET;
	srv.sin_port = htons(sess->port);
	srv.sin_addr = sess->addr;
	sock_connect(sess->ev, sess->fd, &srv, sess, msn_sb_connect_finish);
	return sess;	
}

msn_sb_session_clean(msn_sb_session_t *sess)
{
	if(!sess)
		return;
	xevent_clean(sess->ev);
	sock_close(sess->fd);
	g_string_free_safe(sess->session_id, 1);
	g_string_free_safe(sess->ticket, 1);
	g_string_free_safe(sess->in, 1);
	g_slice_free(msn_sb_session_t, sess);
}

static void msn_sb_connect_finish(int fd, int error, int res, void *data)
{
	msn_sb_session_t *sess = data;

	assert(sess);
	if(error) {
		error_log(errno, "failed to connect sb\n");
		msn_sb_session_clean(sess);
		return ;
	}
	if(sess->type == MSN_SB_TYPE_RNG) 
		msn_ans_send(sess);
	else if(sess->type == MSN_SB_TYPE_XFR)
		msn_command_send(sess->ev, sess->fd, sess, msn_sb_cmd_send_done, \
						"USR %d %s %s\r\n", sess->tr_id++, sess->creator->passport->str, sess->ticket->str);
}

static void msn_ans_send(msn_sb_session_t *s)
{
	GString *out = NULL;

	out = g_string_new(NULL);
	if(!out) {
		msn_sb_session_clean(s);
		return ;
	}
	g_string_printf(out, "ANS %d %s %s %s\r\n", s->tr_id++, s->ms->name->str, \
					s->ticket->str, s->session_id->str);
	sock_write(s->ev, s->fd, msn_ans_send_finish, s, out);
	g_string_free(out, 1);
}

static void msn_ans_send_finish(int fd, int error, int res, void *data)
{
	msn_sb_session_t *s = data;

	if(error) {
		error_log(errno, "failed to send ans\n");
		msn_sb_session_clean(s);
		return;
	}
	xevent_update(s->ev, SOCK_FLAG_READ, s, msn_read_from_sb);
}

static void msn_read_from_sb(int fd, short flag, void *arg)
{
	msn_sb_session_t *s = arg;
	char readbuf[MAX_BUFFER];
	char linebuf[MAX_BUFFER];
	int size_read = -1;
	int size_handled = 0;
	GString *data = NULL;
	int break_loop = 0;

	memset(readbuf, 0, MAX_BUFFER);
	if((size_read = read(fd, readbuf, MAX_BUFFER)) == -1) {
		error_log(errno, "fd %d read failed\n", fd);
		msn_sb_session_clean(s);
		return ;
	}
	if(size_read == 0) {
		/* connection has been reset, how to do */
		debug_log(5, "msn sb close connection %d\n", fd);
		msn_sb_session_clean(s);
		return;
	}
	/* utf-8 validate */
	if(g_utf8_validate(readbuf, size_read, NULL) == FALSE) {
		debug_log(0, "Input {%s} is not utf-8 encoding\n", readbuf);
	}
	g_string_append_len(s->in, readbuf, size_read);
	debug_log(1, "%d bytes, {%s}\n", s->in->len, s->in->str);
	data = s->in;
	/* parse one line from sb */
	for(; size_handled < data->len && break_loop == 0; ) {
		int line_size = msn_line_get(data->str + size_handled, data->len - size_handled, \
						linebuf, MAX_BUFFER - 1);
		if(line_size == 0) {
			debug_log(9, "%s", "continue to read from sb\n");
			break;
		}
		size_handled += line_size;
		switch(msn_sb_command_type_get(linebuf, line_size)) {
			case MSN_SB_ANS:
				break;
			case MSN_SB_MSG: {
				int payload = 0;
			   	
				payload = msn_command_payload(linebuf, line_size);
				if(payload + size_handled > data->len) {
					/* continue to read */
					debug_log(9, "%s", "can't receive msg fully\n");
					size_handled -= line_size;
					break_loop = 1;
				}
				else {
					msn_sb_msg_response(s, data->str + size_handled - line_size, payload + line_size);
					size_handled += payload;
				}
				break;
			}
			case MSN_SB_BYE:
				//msn_sb_session_clean(s);
				return;
			case MSN_SB_USR:
				break;
			default:
				break;
		}
		debug_log(9, "Handled %d bytes\n", size_handled);
	}
	/* reset in */
	if(size_handled < data->len) {
		GString *new_in = g_string_new_len(data->str + size_handled, data->len - size_handled);
		g_string_free(data, 1);
		s->in = new_in;
	}
	else 
		g_string_truncate(s->in, 0);
	debug_log(9, "left %d bytes\n", s->in->len);
}

static void msn_sb_msg_response(msn_sb_session_t *s, const char *buf, const int size)
{
	assert(s && buf);
	debug_log(9, "%d bytes {%s}\n", size, buf);
	header_entry_t *en;
	header_t *headers;
	int header_size = 0;

	if((headers = header_parse(buf, size)) == NULL) {
		debug_log(0, "%s", "failed to parse msg header\n");
		return;
	}
	header_size = headers_end(buf, size);
	en = header_entry_get_by_name(headers, "Content-Type");
	if(!en) 
		return;
	if(strcasestr(en->value, "x-msmsgscontrol")) {
		return;
	}
	else {
		/* send typing information first */
		msn_msg_t *msg = NULL;
		GString *typing = NULL;

		msg = msn_msg_parse(buf, size, s->ms);
		if(!msg) 
			return;
		g_assert(msg->owner);
		msn_msg_insert(msg, msg->owner);
		typing = msn_sb_msg_typing(s);
		if(!typing)
			return;
		debug_log(9, "Typing send %d bytes {%s}\n", typing->len, typing->str);
		sock_write(s->ev, s->fd, msn_sb_msg_typing_send_done, s, typing);
		g_string_free(typing, 1);
	}
}

static void msn_sb_msg_response_done(int fd, int error, int res, void *data) 
{
	msn_sb_session_t *s = data;

	if(error) {
		error_log(errno, "failed to send msg\n");
		msn_sb_session_clean(s);
		return;
	}
	xevent_update(s->ev, SOCK_FLAG_READ, s, msn_read_from_sb);
}

static GString *msn_sb_msg_typing(msn_sb_session_t *s)
{
	if(!s)
		return NULL;
	GString *out = g_string_new(NULL);
	char body[MAX_BUFFER];
	int res = 0;

	res = snprintf(body, MAX_BUFFER - 1, "MIME-Version: 1.0\r\n"\
					"Content-Type: text/x-msmsgscontrol\r\n" \
					"TypingUser: %s\r\n\r\n", s->ms->name->str);
	g_string_printf(out, "MSG %d U %d\r\n", s->tr_id++, res);
	g_string_append_len(out, body, res);
   	return out;	
}

static void msn_sb_msg_typing_send_done(int fd, int error, int res, void *data)
{
	g_assert(data);

	GString *out = g_string_new(NULL);
	msn_sb_session_t *s = data;
	msn_session_t *ms = s->ms;
	GHashTableIter iter;
	gpointer key, value;
	GString *m;
	msn_msg_t *msg;
	msn_user_t *creator = s->creator;
	
	if(error) {
		debug_log(0, "%s", "failed to send typing to sb\n");
		msn_sb_session_clean(s);
		return;
	}
	/* send msg to others */
	g_hash_table_iter_init (&iter, ms->friends);
	while (g_hash_table_iter_next (&iter, &key, &value)) {
		msn_user_t *user = value;
		g_assert(user);
		g_string_append_printf(out, "CAL %d %s\r\n", s->tr_id++, user->passport->str);
	}
	g_assert(creator);
	debug_log(9, "pack %s msg\n", creator->passport->str);
	GList *l = g_list_first(creator->msgs);
	msg = l->data;
	m = msn_msg_pack(msg, s->tr_id++);
	g_string_append_len(out, m->str, m->len);
	debug_log(9, "send {%s}\n", out->str);
	sock_write(s->ev, s->fd, msn_sb_cmd_send_done, s, out);
	g_string_free_safe(out, 1);
	g_string_free_safe(m, 1);
}

static void msn_sb_cmd_send_done(int fd, int error, int res, void *data)
{
	g_assert(data);

	msn_sb_session_t *s = data;
	
	if(error) {
		debug_log(0, "%s", "failed to send typing to sb\n");
		msn_sb_session_clean(s);
		return;
	}
	xevent_update(s->ev, SOCK_FLAG_READ, s, msn_read_from_sb);
}

msn_sb_session_t *msn_sb_session_new(msn_session_t *ms, msn_user_t *user) 
{
	g_return_if_fail(ms);

	msn_sb_session_t *sb = g_slice_new0(msn_sb_session_t);
	sb->creator = user;
	sb->ms = ms;
	return sb;	
}
